/**
 * File: CurtainInputArguments.java
 * Created by: mhaimel
 * Created on: 6 Jul 2009
 * CVS:  $Id: CurtainInputArguments.java,v 1.4 2009/11/24 16:24:52 mhaimel Exp $
 */
package uk.ac.ebi.curtain.configuration;

import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.ReentrantLock;

import org.apache.commons.lang.StringUtils;

import uk.ac.ebi.curtain.utils.concurrent.UtilsCollect;
import uk.ac.ebi.curtain.utils.data.FileType;
import uk.ac.ebi.curtain.utils.data.ReadType;
import uk.ac.ebi.curtain.utils.file.FileInfo;

/**
 * @author mhaimel
 *
 */
public class CurtainInputArguments {

	private final AtomicInteger curtainIteration = new AtomicInteger(1);
	private final AtomicBoolean useContigInput = new AtomicBoolean(false);
	private final Map<String, List<CategoryFileInfo>> category2fileInfo = UtilsCollect.newConcurrentMap();
	private final Map<FileInfo, Integer> fileInfo2cat = UtilsCollect.newConcurrentMap();
	private final AtomicInteger categoryCount = new AtomicInteger(0);
	private final ConcurrentMap<String, Integer> external2internalCategory = UtilsCollect.newConcurrentMap();
	private final Map<Integer, CategoryInfo> cat2Info = UtilsCollect.newConcurrentMap();
	
	private final AtomicInteger kmer = new AtomicInteger();
	private final AtomicReference<File> workingDir = new AtomicReference<File>();
	private final Map<Integer, Integer> minPairCountMap = UtilsCollect.newConcurrentMap();
	private final Map<Integer, Integer> maxPairCountMap = UtilsCollect.newConcurrentMap();
	private final Map<Integer, Integer> maxPartialCountMap = UtilsCollect.newConcurrentMap();
	private final CopyOnWriteArrayList<Integer> batchList = new CopyOnWriteArrayList<Integer>();
	
	private final AtomicReference<String> startpoint = new AtomicReference<String>(StringUtils.EMPTY);
	private final AtomicBoolean distanceInfo = new AtomicBoolean(true);
	private final AtomicInteger filterCovMin = new AtomicInteger(0);
	private final AtomicInteger filterCovMax = new AtomicInteger(-1);
	private final AtomicBoolean velvetForce = new AtomicBoolean(false);
	private final AtomicBoolean velvetUnpaired = new AtomicBoolean(false);
	private final AtomicInteger velvetMinPairCount = new AtomicInteger(-1);
	private final AtomicBoolean velvetCoverageCalc = new AtomicBoolean(false);
	private final AtomicInteger mappingQualityCutoff = new AtomicInteger(20);
	
	
	public CurtainInputArguments() {
		super();
	}	

	public void setKmer(Integer kmer) {
		this.kmer.set(kmer);
	}

	public Integer getKmer() {
		return kmer.get();
	}
	
	public void setCurtainIteration(Integer curtainIteration) {
		this.curtainIteration.set(curtainIteration);
	}
	
	public Integer getCurtainIteration() {
		return curtainIteration.get();
	}
	
	public AtomicBoolean getVelvetForce() {
		return velvetForce;
	}
	public AtomicBoolean getVelvetUnpaired() {
		return velvetUnpaired;
	}

	public void setWorkingDir(File workingDir) {
		this.workingDir.set(workingDir);
	}

	public File getWorkingDir() {
		return workingDir.get();
	}

	public void setMinPairCount(String externalId,Integer minPairCount) {
		Integer id = translateCategory(externalId);
		this.minPairCountMap.put(id,minPairCount);
	}

	public Integer getMinPairCount(Integer category) {
		return minPairCountMap.get(category);
	}

	public void setMaxPairCount(String externalId,Integer maxPairCount) {
		Integer id = translateCategory(externalId);
		this.maxPairCountMap.put(id,maxPairCount);
	}

	public Integer getMaxPairCount(Integer category) {
		return maxPairCountMap.get(category);
	}
	
	public Integer getMinPairCountDefault(){
		return 5;
	}
	
	public void setMaxPartialCount(String externalId,Integer minPairCount) {
		Integer id = translateCategory(externalId);
		this.maxPartialCountMap.put(id,minPairCount);
	}

	public Integer getMaxPartialCount(Integer category) {
		return maxPartialCountMap.get(category);
	}

	public List<Integer> getBatchList() {
		return batchList;
	}
	
	public void addBatchList(Integer bucket){
		batchList.add(bucket);
	}

	public void addFile(FileInfo fileInfo) {
		addFile(StringUtils.EMPTY,fileInfo);
	}	
	
	public List<FileInfo> getFiles(FileType ... fType){
		List<FileInfo> fi = new ArrayList<FileInfo>();
		for(ReadType rType : ReadType.values()){
			fi.addAll(getFiles(rType, fType));
		}
		return fi;
	}
	
	public List<FileInfo> getFiles(ReadType rType,FileType ... fType){
		List<FileInfo> fi = new ArrayList<FileInfo>();
		for(FileType ft : fType){
			List<CategoryFileInfo> list = getFileList(buildKey(rType,ft));
			if(list != null){
				for(CategoryFileInfo cfi : list){
					fi.add(cfi.getFileInfo());
				}
			}
		}
		return fi;
	}
	
	public Map<Integer, Integer> getCategory2InsertLength(){
		Map<Integer, Integer> cat2insert = new HashMap<Integer, Integer>();
		for(Entry<Integer, CategoryInfo> info : cat2Info.entrySet()){
			cat2insert.put(info.getKey(), info.getValue().getInsertLength());
		}
		return cat2insert;
	}
	
	public Map<Integer, Integer> getCategory2InsertLengthSd(){
		Map<Integer, Integer> cat2insertsd = new HashMap<Integer, Integer>();
		for(Entry<Integer, CategoryInfo> info : cat2Info.entrySet()){
			if(info.getValue().getInsertLengthSd()>=0){
				cat2insertsd.put(info.getKey(), info.getValue().getInsertLengthSd());
			}
		}
		return cat2insertsd;
	}
	
	public void addFile(String extCat, FileInfo fileInfo) {
		Integer id = translateCategory(extCat);
		addFile2Category(id, fileInfo);
	}
	

	public void addFile2Category(Integer category, FileInfo fileInfo) {
		List<CategoryFileInfo> list = getFileList(fileInfo);
		if(null == list){
			list = new ArrayList<CategoryFileInfo>();
			setFileList(fileInfo, list);
		}
		CategoryFileInfo catInfo = new CategoryFileInfo(fileInfo,category);
		if(!list.contains(catInfo)){
			list.add(catInfo);
			fileInfo2cat.put(fileInfo, category);
		}
	}
	
	public int getVelvetMinPairCount() {
		return velvetMinPairCount.get();
	}
	
	public void setVelvetMinPairCount(int velvetMinPairCount) {
		this.velvetMinPairCount.set(velvetMinPairCount);
	}
	
	public AtomicBoolean getVelvetCoverageCalc() {
		return velvetCoverageCalc;
	}
		
	private List<CategoryFileInfo> getFileList(String key){
		return category2fileInfo.get(key);
	}
	
	private List<CategoryFileInfo> getFileList(FileInfo fileInfo){
		return getFileList(buildKey(fileInfo));
	}
	
	private void setFileList(FileInfo fi, List<CategoryFileInfo> list){
		category2fileInfo.put(buildKey(fi), list);
	}
	

	public void addCategory(Integer category, String id) {
		lock.lock();
		try{
			external2internalCategory.put(id, category);
		}finally{
			if(lock.isHeldByCurrentThread()){
				lock.unlock();
			}
		}		
	}
	
	private final ReentrantLock lock = new ReentrantLock();
	
	private Integer translateCategory(String externalId){
		if(StringUtils.isBlank(externalId)){
			return -1;
		}
		Integer internalId = external2internalCategory.get(externalId);
		if(null == internalId){
			lock.lock();
			try{
				while (true){
					internalId = external2internalCategory.get(externalId);
					if( null != internalId){
						break;
					}
					Integer id = categoryCount.getAndIncrement();
					external2internalCategory.put(externalId, id);
				}
			}finally{
				if(lock.isHeldByCurrentThread()){
					lock.unlock();
				}
			}
		}
		return internalId;
	}

	public void addInsertLength(String extId, Integer insertLength) {
		Integer id = translateCategory(extId);
		addInsertLength2Category(id, insertLength);
	}
	

	public void addInsertLength2Category(Integer category, Integer insertLength) {
		CategoryInfo info = getCategoryInfo(category);
		if(null == info){
			Integer sd = Double.valueOf(insertLength*0.1).intValue();
			info = new CategoryInfo(category,insertLength,sd);
		} else {
			info = new CategoryInfo(info.getCategory(),insertLength,info.getInsertLengthSd());
		}
		setCategoryInfo(info);
	}
	
	public void addInsertLengthSd(String extId, Integer sd) {
		Integer id = translateCategory(extId);
		CategoryInfo info = getCategoryInfo(id);
		if(null == info){
			info = new CategoryInfo(id,-1,sd);
		} else {
			info = new CategoryInfo(info.getCategory(),info.getInsertLength(),sd);
		}
		setCategoryInfo(info);
	}
	

	public void addInsertLengthSd2Category(Integer category, Integer sd) {
		CategoryInfo info = getCategoryInfo(category);
		if(null == info){
			info = new CategoryInfo(category,-1,sd);
		} else {
			info = new CategoryInfo(info.getCategory(),info.getInsertLength(),sd);
		}
		setCategoryInfo(info);
	}
	
	public boolean hasInsertLength(FileInfo readFile){
		return getInsertLength(readFile) != null;
	}
	

	public Integer getDefaultInsertLengthSd(FileInfo readFile) {
		CategoryInfo info = getCategoryInfo(readFile);
		if(null == info){
			return null;
		}
		Integer insLen = info.getInsertLength();
		return defaultSd(insLen);
	}

	public Integer getInsertLengthSd(FileInfo readFile) {
		CategoryInfo info = getCategoryInfo(readFile);
		if(null == info){
			return null;
		}
		Integer sd = info.getInsertLengthSd();
		return sd > 0?sd:null;
	}
	
	public Integer getInsertLength(FileInfo readFile) {
		CategoryInfo info = getCategoryInfo(readFile);
		if(null == info){
			return null;
		}
		Integer insLen = info.getInsertLength();
		return insLen>0?insLen:null;
	}
	
	private CategoryInfo getCategoryInfo(FileInfo readFile){
		Integer cat = getCategory(readFile);
		if(null == cat){
			return null;
		} 
		CategoryInfo info = getCategoryInfo(cat);
		if(null == info){
			return null;
		}
		return info;
	}
	
	
	public Integer getCategory(FileInfo file){
		return fileInfo2cat.get(file);
	}
	
	public Set<Integer> getCategories(){
		return new HashSet<Integer>(cat2Info.keySet());
	}

	private void setCategoryInfo(CategoryInfo info) {
		cat2Info.put(info.getCategory(), info);
	}

	private CategoryInfo getCategoryInfo(Integer catId) {
		return cat2Info.get(catId);
	}

	public void setStartpoint(String startpoint) {
		this.startpoint.set(startpoint);
	}

	public String getStartpoint() {
		return startpoint.get();
	}
	
	private Integer defaultSd(Integer insertLength) {
//		return -1;
		return Long.valueOf(Math.round(insertLength.intValue()*0.2)).intValue();
	}

	private String buildKey(FileInfo fi){
		return buildKey(fi.getReadType(),fi.getFileType());
	}
	
	private String buildKey(ReadType rt, FileType ft){
		return rt.name() + "_"+ft.name();
	}


	private static class CategoryInfo{
		private final Integer category;
		private final Integer insertLength;
		private final Integer insertLengthSd;
		
		public CategoryInfo(Integer category, Integer insertLength,
				Integer insertLengthSd) {
			super();
			this.category = category;
			this.insertLength = insertLength;
			this.insertLengthSd = insertLengthSd;
		}

		public Integer getCategory() {
			return category;
		}

		public Integer getInsertLength() {
			return insertLength;
		}

		public Integer getInsertLengthSd() {
			return insertLengthSd;
		}	
		
	}
	
	private static class CategoryFileInfo{
		private final FileInfo fileInfo;
		private final Integer category;
		
		public CategoryFileInfo(FileInfo fileInfo, Integer category) {
			super();
			this.fileInfo = fileInfo;
			this.category = category;
		}
		public FileInfo getFileInfo() {
			return fileInfo;
		}
		public Integer getCategory() {
			return category;
		}
		public String toString() {
			return fileInfo.toString();
		}
		public File getFile() {
			return fileInfo.getFile();
		}
		public FileType getFileType() {
			return fileInfo.getFileType();
		}
		public String getName() {
			return fileInfo.getName();
		}
		public ReadType getReadType() {
			return fileInfo.getReadType();
		}	
		@Override
		public boolean equals(Object obj) {
			if (this == obj)
				return true;
			if (obj == null)
				return false;
			if (getClass() != obj.getClass())
				return false;
			CategoryFileInfo other = (CategoryFileInfo) obj;
			if (category == null) {
				if (other.category != null)
					return false;
			} else if (!category.equals(other.category))
				return false;
			if (fileInfo == null) {
				if (other.fileInfo != null)
					return false;
			} else if (!fileInfo.equals(other.fileInfo))
				return false;
			return true;
		}
		@Override
		public int hashCode() {
			final int prime = 31;
			int result = 1;
			result = prime * result
					+ ((category == null) ? 0 : category.hashCode());
			result = prime * result
					+ ((fileInfo == null) ? 0 : fileInfo.hashCode());
			return result;
		}
	}

	public void setUseContigInput(boolean b) {
		this.useContigInput.set(b);
	}

	public boolean getUseContigInput() {
		return useContigInput.get();
	}

	public void setDistanceInfo(boolean b) {
		this.distanceInfo.set(b);
	}
	
	public boolean getDistanceInfo() {
		return distanceInfo.get();
	}

	public void setFilterCovMin(int filterCovMin) {
		this.filterCovMin.set(filterCovMin);
	}
	
	public void setFilterCovMax(int filterCovMax) {
		this.filterCovMax.set(filterCovMax);
	}

	public AtomicInteger getFilterCovMin() {
		return filterCovMin;
	}
	
	public AtomicInteger getFilterCovMax() {
		return filterCovMax;
	}

	public AtomicInteger getMappingQualityCutoff() {
		return mappingQualityCutoff;
	}

}
